/**
 * 
 */
package com.huxi.lang.local.support;

import static java.nio.charset.StandardCharsets.UTF_8;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.time.LocalDate;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import com.huxi.lang.local.FestivalStrategy;
import com.huxi.lang.type.VTypeUtils;

/**
 * 法定节假日策略抽象实现
 * 
 * @since 1.0
 * @version 1.0
 * @author jian.wu
 */
public class ClassPathFestivalStrategy implements FestivalStrategy {

	public static final class RangeEntry {
		protected final LocalDate begin;
		protected volatile LocalDate end;
		protected volatile boolean failed;

		public RangeEntry(LocalDate begin, LocalDate end) {
			this.begin = Objects.requireNonNull(begin);
			this.end = Objects.requireNonNull(end);
			this.failed = false;
		}
	}

	public static final String csvFilePath = "com/huxi/lang/local/festival/";
	public static final String csvFileName = "festival-%tY.csv";

	private final ConcurrentMap<Integer, RangeEntry> rangeCache = new ConcurrentHashMap<>();
	private final ConcurrentMap<LocalDate, FestivalEntry> entryCache = new ConcurrentHashMap<>();
	private final Locale locale;
	private final String path;

	public ClassPathFestivalStrategy(Locale locale) {
		this.locale = locale;
		this.path = csvFilePath + (locale == null ? "" : locale.getCountry().toLowerCase() + "/") + csvFileName;
	}

	public Locale getLocale() {
		return locale;
	}

	public String getPath() {
		return path;
	}

	@Override
	public FestivalState getFestivalDate(LocalDate localDate) throws IllegalStateException {
		Objects.requireNonNull(localDate);
		RangeEntry range = rangeCache.get(localDate.getYear());
		if (range == null) {
			Exception en = null;
			synchronized (csvFilePath) {
				range = rangeCache.get(localDate.getYear());
				if (range == null) {
					LocalDate begin = LocalDate.of(localDate.getYear(), 1, 1);
					LocalDate end = LocalDate.of(localDate.getYear() + 1, 1, 1);
					range = new RangeEntry(begin, end);
					try {
						Collection<FestivalEntry> festivals = load(range);
						if (festivals == null) {
							en = new NullPointerException(String.format("resource not result ~ [%s,%s)",
									range.begin.getYear(), range.end.getYear()));
							range = null;
						} else {
							for (FestivalEntry entry : festivals) {
								entryCache.put(entry.getDate(), entry);
							}
						}
					} catch (Exception ex) {
						range.failed = true;
						en = ex;
					}
					if (range != null) {
						for (int i = begin.getYear(), m = end.getYear(); i < m; i++) {
							rangeCache.put(i, range);
						}
					}
				}
			}
			if (en != null) {
				throw new IllegalStateException(
						String.format("resource not exist ~ [%s,%s)", range.begin.getYear(), range.end.getYear()), en);
			}
		}
		if (range.failed) {
			throw new IllegalStateException(
					String.format("resource not exist ~ [%s,%s)", range.begin.getYear(), range.end.getYear()));
		}
		FestivalEntry f = entryCache.get(localDate);
		return f == null ? null : f.getFestival();
	}

	protected Collection<FestivalEntry> load(RangeEntry range) throws Exception {
		String path = String.format(this.path, range.begin);
		InputStream in = ClassLoader.getSystemResourceAsStream(path);
		if (in == null) {
			throw new FileNotFoundException(String.format("resource not exist - classpath:/%s", path));
		}
		try {
			return loadToCollection(in);
		} catch (Exception ex) {
			throw new RuntimeException(String.format("resource load failed - classpath:/%s", path), ex);
		} finally {
			try {
				in.close();
			} catch (Throwable th) {
			}
		}
	}

	/**
	 * 解析法定节日项
	 * 
	 * @param line 行数据
	 * @return 法定节日项
	 * @throws RuntimeException 行数据解析失败
	 * @since 1.0
	 */
	protected FestivalEntry parseCsvLine(String line) throws RuntimeException {
		String[] items = line.replaceAll("\\s+", "").split(",");
		if (items.length == 0) {
			// 空行被直接忽略
			return null;
		}
		try {
			if (items.length < 3 || items.length > 4) {
				throw new RuntimeException("每行必须有且仅有3个字段 - " + items.length);
			}
			LocalDate date = LocalDate.parse(items[0]);
			FestivalState festival = VTypeUtils.enumParse(FestivalState.class, items[1]);
			String detail = items[2];
			FestivalEntry entry = new FestivalEntry();
			entry.setDate(date);
			entry.setFestival(festival);
			entry.setDetail(detail);
			return entry;
		} catch (Exception ex) {
			throw new RuntimeException("无效行内容 - " + line, ex);
		}
	}

	/**
	 * 解析CSV内容生成假日策略项列表
	 * 
	 * @param in CSV内容输入流
	 * @return 假日策略项列表
	 * @throws Exception CSV内容解析失败
	 * @since 1.0
	 */
	protected Collection<FestivalEntry> loadToCollection(InputStream in) throws Exception {
		final Map<LocalDate, FestivalEntry> map = new LinkedHashMap<>();
		try (BufferedReader reader = new BufferedReader(new InputStreamReader(in, UTF_8))) {
			String line = reader.readLine();
			if (line != null) {
				while ((line = reader.readLine()) != null) {
					FestivalEntry entry = parseCsvLine(line);
					if (entry != null) {
						LocalDate key = entry.getDate();
						if (map.put(key, entry) != null) {
							throw new RuntimeException("无效行内容，不能存在重复日期假日项 - " + line);
						}
					}
				}
			}
		}
		return map.values();
	}

}
